<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button onclick="down()">下载</button>
</body>

<script>

  /**
   * 文件下载
   * @param {String} url 
   * @param {Object} options 
   * @param {Object} options.id 下载目录ID，默认fileDownload
   * @param {Object} options.fileSize 下载文件阀值，默认50M，文件超过次大小则使用fileApi下载
   * @param {Object} options.fetch
   * @param {Object} options.onProgress 下载进度回调
   */
  async function fileDownload(url, options) {
    let ops = {
      id: 'fileDownload',
      onProgress() { },
      // 50M
      fileSize: 50 * 1024 * 1024,
    };
    Object.assign(ops, options);


    const controller = new AbortController();

    const response = await fetch(url, {
      signal: controller.signal,
      ...ops.fetch,
    });

    response.controller = controller;

    if (!response.ok) {
      throw new Error(`网络响应失败，状态码: ${response.status}`);
    }



    const contentLength = response.headers.get('content-length');
    let total = contentLength ? parseInt(contentLength, 10) : null;

    if (total < ops.fileSize) {
      return generalDownload(response, total, ops);
    } else {
      return fileApiDownload(response, total, ops);
    }
  }

  async function generalDownload(response, total, ops) {
    const filename = getFileNameFromResponse(response, ops);

    let loaded = 0;
    let reader = response.body.getReader();
    let chunks = [];

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      chunks.push(value);
      loaded += value.length;

      if (total !== null) {
        const percentComplete = (loaded / total) * 100;
        ops.onProgress(percentComplete, total, loaded);
      } else {
        ops.onProgress(0, 0, 0); // 如果无法获取总大小，则传递 null 表示进度未知
      }
    }

    let contentType = response.headers.get('content-type');
    let blob = new Blob(chunks, { type: contentType });

    let a = document.createElement('a');
    let url = URL.createObjectURL(blob);
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
    reader.cancel();
    url = null;
    reader = null;
    chunks = [];
    blob = null;
    a.href = '';
    a = null;
  }

  async function fileApiDownload(response, total, ops) {
    if (!(window.showDirectoryPicker instanceof Function)) {
      console.warn('当前浏览器不支持showDirectoryPicker');
      return generalDownload(response, total, ops);
    }
    let filename = getFileNameFromResponse(response, ops);
    let fileHandle = await showSaveFilePicker({ startIn: 'downloads', suggestedName: filename });
    let fileWriter = await fileHandle.createWritable();

    // 创建 TransformStream 来监听进度
    const progressStream = new TransformStream({
      loaded: 0,
      total,
      transform(chunk, controller) {
        this.loaded += chunk.length;
        if (total !== null) {
          const percentComplete = (this.loaded / this.total) * 100;
          ops.onProgress(percentComplete, this.total, this.loaded);
        }
        controller.enqueue(chunk);
      }
    });

    return response.body.pipeThrough(progressStream).pipeTo(fileWriter);
  }

  function getFileNameFromResponse(response, options) {
    if (options.filename) {
      return options.filename;
    }
    let filename = response.headers.filename;
    if (!filename && response.headers['content-disposition']) {
      let h = response.headers['content-disposition'];
      filename = h.substr(h.indexOf('filename=') + 9);
    }
    if (filename) {
      return decodeURI(filename);
    } else {
      let path = new URL(response.url).pathname;
      return path.substring(path.lastIndexOf('/') + 1);
    }
  }
</script>

<script>
  function down(){
    fileDownload('http://127.0.0.1:81/qax_zyj_off.zip?t=' + new Date().getTime(), {
      onProgress(percentComplete, total, loaded) {
        console.log(`下载进度：${percentComplete}%`);
      },
    }).then(() => {
      console.log('下载完成');
    }).catch(console.log);
  }
</script>

</html>